package compiler

// SymbolScope 符号范围
type SymbolScope string

// 符号范围常量
const (
	GlobalScope   SymbolScope = "GLOBAL"   // 全局常量
	LocalScope    SymbolScope = "LOCAL"    // 局部变量
	BuiltinScope  SymbolScope = "BUILTIN"  // 内置函数变量
	FreeScope     SymbolScope = "FREE"     // 自由变量
	FunctionScope SymbolScope = "FUNCTION" // 函数变量
)

// Symbol 符号结构体
type Symbol struct {
	Name  string      // 名称
	Scope SymbolScope // 范围
	Index int         // 索引
}

// SymbolTable 符号表结构体。 符号表: 解释器和编译器中用于将标识符与信息相关联的数据结构。它可以用在从词法分析到代码生成的所有阶段，作用是存储和检索有关标识符（也被称为符号）的信息，
//
//	比如标识符的位置、所在作用域、是否已经被定义、绑定值的类型，以及解释过程和编译过程中的有用信息。
//
// 我们将使用符号表来关联标识符与作用域和特定的数字。
// 现在，它必须完成两件事:
//
//	·将全局范围内的标识符与特定的数字相关联；
//	·获取已与给定标识符相关联的数字。
//
// 以上两件事在符号表中分别称为“定义”和“解析”。
// 在已知作用域内“定义”一个标识符并为其关联部分信息，随后将这个标识符“解析”成之前关联的信息。
// 我们将这些信息称为“符号”。标识符与包含信息的符号相关。
type SymbolTable struct {
	Outer *SymbolTable // 外层符号表

	store         map[string]Symbol // key 是变量名 val 是变量信息
	numDefinition int               // 当前符号表中定义了多少符号

	FreeSymbols []Symbol // 自由变量
}

// NewSymbolTable 构建一个全新的符号表指针
func NewSymbolTable() *SymbolTable {
	return &SymbolTable{
		store:       make(map[string]Symbol),
		FreeSymbols: []Symbol{},
	}
}

// NewEnclosedSymbolTable 构建一个嵌套符号表
func NewEnclosedSymbolTable(outer *SymbolTable) *SymbolTable {
	s := NewSymbolTable()
	s.Outer = outer
	return s
}

// DefineBuiltin 定义内置函数符号
func (s *SymbolTable) DefineBuiltin(index int, name string) Symbol {
	symbol := Symbol{Name: name, Index: index, Scope: BuiltinScope}
	s.store[name] = symbol
	return symbol
}

// Define 定义符号
func (s *SymbolTable) Define(name string) Symbol {
	// 如果已经存在了就不进行重复定义了
	symbol, ok := s.Resolve(name)
	if ok {
		return symbol
	}

	symbol = Symbol{Name: name, Index: s.numDefinition}

	// 如果没有外层符号表，那么说明这个符号表就是最外层的，是全局符号表
	if s.Outer == nil {
		symbol.Scope = GlobalScope
	} else {
		symbol.Scope = LocalScope
	}

	s.store[name] = symbol
	s.numDefinition++

	return symbol
}

// Resolve 解析符号
func (s *SymbolTable) Resolve(name string) (Symbol, bool) {
	sym, ok := s.store[name]
	if !ok && s.Outer != nil {
		obj, ok := s.Outer.Resolve(name)
		if !ok {
			return obj, ok
		}
		if obj.Scope == GlobalScope || obj.Scope == BuiltinScope {
			return obj, ok
		}
		free := s.defineFree(obj)
		return free, true
	}
	return sym, ok
}

func (s *SymbolTable) defineFree(original Symbol) Symbol {
	s.FreeSymbols = append(s.FreeSymbols, original)

	symbol := Symbol{Name: original.Name, Scope: FreeScope, Index: len(s.FreeSymbols) - 1}
	symbol.Scope = FreeScope

	s.store[original.Name] = symbol
	return symbol
}

func (s *SymbolTable) DefineFunctionName(name string) Symbol {
	symbol := Symbol{Name: name, Index: 0, Scope: FunctionScope}
	s.store[name] = symbol
	return symbol
}
